/*-------------------------------------------------------------------------
 *
 * basebackup_progress.c
 *	  基备份接收器实现进度跟踪，包括但不限于命令进度报告。
 *
 * 即使未指定复制命令 BASE_BACKUP 的 PROGRESS 选项，
 * 也应该使用此功能。没有该选项，我们将无法统计出将需要
 * 备份的文件大小，但我们仍然可以向命令进度报告设施提供
 * 我们处理的数据量。
 *
 * 此外，我们还使用这个方便的地方来更新 bbsink_state 的某些
 * 字段。这项工作准确地描述为跟踪我们的进度，但不仅仅用于
 * 内省。我们需要正确更新这些字段，以便基备份正常工作。
 *
 * 这个特定的基备份接收器需要额外的回调，而大多数基备份
 * 接收器不需要。不将这些塞入接口中，而是在这里有几个
 * 额外的函数可以让 basebackup.c 调用。（我们可以将逻辑直接放入
 * 那个文件中，因为它相对简单，但将与进度报告相关的所有内容
 * 放在一个地方似乎更整洁。）
 *
 * Portions Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/backup/basebackup_progress.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "backup/basebackup.h"
#include "backup/basebackup_sink.h"
#include "commands/progress.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/latch.h"
#include "utils/timestamp.h"

static void fc_bbsink_progress_begin_backup(bbsink *fc_sink);
static void fc_bbsink_progress_archive_contents(bbsink *fc_sink, size_t fc_len);
static void fc_bbsink_progress_end_archive(bbsink *fc_sink);

static const bbsink_ops bbsink_progress_ops = {
	.begin_backup = fc_bbsink_progress_begin_backup,
	.begin_archive = bbsink_forward_begin_archive,
	.archive_contents = fc_bbsink_progress_archive_contents,
	.end_archive = fc_bbsink_progress_end_archive,
	.begin_manifest = bbsink_forward_begin_manifest,
	.manifest_contents = bbsink_forward_manifest_contents,
	.end_manifest = bbsink_forward_end_manifest,
	.end_backup = bbsink_forward_end_backup,
	.cleanup = bbsink_forward_cleanup
};

/*
 * 创建一个新的基础备份接收器，该接收器执行进度跟踪功能并将数据转发给后续接收器。
 */
bbsink * bbsink_progress_new(bbsink *fc_next, bool fc_estimate_backup_size)
{
	bbsink	   *fc_sink;

	Assert(fc_next != NULL);

	fc_sink = palloc0(sizeof(bbsink));
	*((const bbsink_ops **) &fc_sink->bbs_ops) = &bbsink_progress_ops;
	fc_sink->bbs_next = fc_next;

	/*
 * 报告基础备份正在进行中，并将备份的总大小设置为 -1，这将被转换为 NULL。如果我们在估算备份大小，当我们获得真实估算时将其插入。
 */
	pgstat_progress_start_command(PROGRESS_COMMAND_BASEBACKUP, InvalidOid);
	pgstat_progress_update_param(PROGRESS_BASEBACKUP_BACKUP_TOTAL, -1);

	return fc_sink;
}

/*
 * 备份开始时的进度报告。
 */
static void fc_bbsink_progress_begin_backup(bbsink *fc_sink)
{
	const int	fc_index[] = {
		PROGRESS_BASEBACKUP_PHASE,
		PROGRESS_BASEBACKUP_BACKUP_TOTAL,
		PROGRESS_BASEBACKUP_TBLSPC_TOTAL
	};
	int64		fc_val[3];

	/*
 * 报告我们现在正在作为基础备份流式传输数据库文件。还要宣传表空间的数量，以及，如果已知，估计的总备份大小。
 */
	fc_val[0] = PROGRESS_BASEBACKUP_PHASE_STREAM_BACKUP;
	if (fc_sink->bbs_state->bytes_total_is_valid)
		fc_val[1] = fc_sink->bbs_state->bytes_total;
	else
		fc_val[1] = -1;
	fc_val[2] = list_length(fc_sink->bbs_state->tablespaces);
	pgstat_progress_update_multi_param(3, fc_index, fc_val);

	/* 委托给下一个接收器。 */
	bbsink_forward_begin_backup(fc_sink);
}

/*
 * 归档结束的进度报告。
 */
static void fc_bbsink_progress_end_archive(bbsink *fc_sink)
{
	/*
 * 我们期望每个表空间一个归档，因此达到归档的结束也意味着达到表空间的结束。（总有一天我们可能有理由将这些概念解耦。）
 *
 * 如果 WAL 包含在备份中，我们将在最后一个归档完成之前标记最后一个表空间为完成，因此我们需要在这里添加一个保护，以确保流式传输的表空间数量不超过总数。
 */
	if (fc_sink->bbs_state->tablespace_num < list_length(fc_sink->bbs_state->tablespaces))
		pgstat_progress_update_param(PROGRESS_BASEBACKUP_TBLSPC_STREAMED,
									 fc_sink->bbs_state->tablespace_num + 1);

	/* 委托给下一个接收器。 */
	bbsink_forward_end_archive(fc_sink);

	/*
 * 这是一个方便的地方来更新 bbsink_state 对当前表空间的概念。请注意，bbsink_state 对象在所有相关的 bbsink 对象之间共享，但我们是最外层的，并且这是我们所做的最后一项工作。
 */
	fc_sink->bbs_state->tablespace_num++;
}

/*
 * 处理新归档内容的进度跟踪。
 *
 * 将已经流式传输的数据量计数器增加给定的字节数，并更新 pg_stat_progress_basebackup 的进度报告。
 */
static void fc_bbsink_progress_archive_contents(bbsink *fc_sink, size_t fc_len)
{
	bbsink_state *fc_state = fc_sink->bbs_state;
	const int	fc_index[] = {
		PROGRESS_BASEBACKUP_BACKUP_STREAMED,
		PROGRESS_BASEBACKUP_BACKUP_TOTAL
	};
	int64		fc_val[2];
	int			fc_nparam = 0;

	/* 首先用完成的字节数更新 bbsink_state。 */
	fc_state->bytes_done += fc_len;

	/* 现在转发给下一个接收器。 */
	bbsink_forward_archive_contents(fc_sink, fc_len);

	/* 准备设置命令进度报告的完成字节数。 */
	fc_val[fc_nparam++] = fc_state->bytes_done;

	/*
 * 我们也可能想要更新总字节数，以避免超过 100% 或总大小。当我们接近备份的结束时，这可能会使总大小数字发生变化（如果包含 WAL，则估算总是错误的），但这总比完成列大于总数要好。
 */
	if (fc_state->bytes_total_is_valid && fc_state->bytes_done > fc_state->bytes_total)
		fc_val[fc_nparam++] = fc_state->bytes_done;

	pgstat_progress_update_multi_param(fc_nparam, fc_index, fc_val);
}

/*
 * 宣布我们正在等待备份开始的检查点。
 */
void basebackup_progress_wait_checkpoint(void)
{
	pgstat_progress_update_param(PROGRESS_BASEBACKUP_PHASE,
								 PROGRESS_BASEBACKUP_PHASE_WAIT_CHECKPOINT);
}

/*
 * 宣布我们正在估算备份大小。
 */
void basebackup_progress_estimate_backup_size(void)
{
	pgstat_progress_update_param(PROGRESS_BASEBACKUP_PHASE,
								 PROGRESS_BASEBACKUP_PHASE_ESTIMATE_BACKUP_SIZE);
}

/*
 * 宣布我们正在等待备份结束的 WAL 归档。
 */
void basebackup_progress_wait_wal_archive(bbsink_state *fc_state)
{
	const int	fc_index[] = {
		PROGRESS_BASEBACKUP_PHASE,
		PROGRESS_BASEBACKUP_TBLSPC_STREAMED
	};
	int64		fc_val[2];

	/*
 * 在这一点上，我们报告已经完成所有表空间，即使主表空间的归档仍然是打开的，因为即将添加的是 WAL 文件，而不是实际来自主表空间的文件。
 */
	fc_val[0] = PROGRESS_BASEBACKUP_PHASE_WAIT_WAL_ARCHIVE;
	fc_val[1] = list_length(fc_state->tablespaces);
	pgstat_progress_update_multi_param(2, fc_index, fc_val);
}

/*
 * 宣布我们正在将 WAL 文件转移到最后的归档中。
 */
void basebackup_progress_transfer_wal(void)
{
	pgstat_progress_update_param(PROGRESS_BASEBACKUP_PHASE,
								 PROGRESS_BASEBACKUP_PHASE_TRANSFER_WAL);
}

/*
 * 宣布我们不再执行备份。
 */
void basebackup_progress_done(void)
{
	pgstat_progress_end_command();
}
